사용자 레벨 조정 작업은 중간에 문제가 발생해서 작업이 중단된다면 그때까지 진행된 변경 작업도 모두 취소시키도록 결정했다.

모 아니면 도

테스트용 UserService 대역

테스트를 만들어 예외를 강제로 만들어 작업이 중단된 채로 남아 있을때 어떻게 해야 하는지 확인해보자. 작업 중간에 예외를 강제로 만들기 위해 테스트용 UserService의 대역을 만든다. 간단히 UserService를 상속해서 테스트에 필요한 기능을 추가하도록 일부 메소드를 오버라이딩하는 방법을 사용하자.

```java

static class TestUserService extends UserService { private String id;

    private TestUserService(String id) {
        this.id = id;
    }

    protected void upgradeLevel(User user){
        if (user.getId().equals(this.id)) throw new TestUserServiceException();
        super.upgradeLevel(user);
    }
}

### 강제 예외 발생을 통한 테스트 

테스트의 목적은 사용자 레벨 업그레이드를 시도하다가 중간에 예외가 발생했을 경우, 그 전에 업그레이드했던 사용자도 다시 원래 상태로 돌아갔는지를 확인하는 것이다. 
우리가 기대하는 건 네 번째 사용자를 처리하다가 예외가 발생해서 작업이 중단됐으니 이미 레벨을 수정했던 두 번째 사용자도 원래 상태로 돌아가는 것이다. 

하지만 결과는 실패로, 예외가 발생해도 이전에 처리했던 것은 돌아가지 않는다. 

### 테스트 실패의 원인 

이는 트랜잭션의 문제로, 모든 사용자의 레벨을 업그레이드하는 작업인 `upgradeLevels()`의 메소드가 하나의 트랜잭션 안에서 동작하지 않았기 때문이다. 
트랜잭션이란 더 이상 나눌 수 없는 단위 작업을 말하는데, 작업을 쪼개서 작은 단위로 만들 수 없다는 것은 트랜잭션의 핵심 속성인 원자성을 의미한다. 
`upgradeLevels()`메소드의 작업은 트랜잭션이 적용되지 않았기 때문에 새로 추가된 기술 요건을 만족하지 못하고, 이를 검증하기 위해 만든 테스트가 실패하는 것이다. 

## 트랜잭션 경계 설정 

DB는 완벽한 트랜잭션을 보장하는데, 다중 로우의 수정이나 삭제를 위한 요청을 했을 때 일부만 처리되고 일부는 누락되는 경우는 없다. 
하지만 여러 개의 SQL이 사용되는 작업을 하나의 트랜잭션으로 취급하는 경우에는 트랜잭션의 경계설정을 해줘서 SQL문 끼리의 작업 도중 취소 되었을 때 롤백을 하거나, 완료가 되면 커밋으로 성공을 알리는 등의 작업을 해야한다. 

### JDBC 트랜잭션의 트랜잭션 경계설정 

트랜잭션의 시작과 종료는 `Connection` 오브젝트를 통해 이루어진다. 
JDBC에서 자동커밋 옵션을 false로 만들어주면 새로운 트랜잭션이 시작되고, 
`commit()` 또는 `rollback()` 메소드가 호출될 때까지의 작업이 하나의 트랜잭션으로 묶인다. 

### `UserSErvice`와 `UserDao`의 트랜잭션 문제 

현재 코드는 데이터 액세스 코드를 DAO로 만들어서 분리해 놓았기 때문에, DAO메소드를 호출할 때마다 하나의 새로운 트랜잭션이 만들어지는 구조가 될 수 밖에 없다. 
결국, DAO를 사용하면 비즈니스 로직을 담고 있는 UserService 내에서 진행되는 여러 가지 작업을 하나의 트랜잭션으로 묶는 일이 불가능해진다. 

### 비즈니스 로직 내의 트랜잭션 경계 설정 

1. DAO 메소드 안으로 `upgradeLevels()`메소드의 내용을 옮기는 방법  
**-> 비즈니스 로직과 데이터 로직을 한데 묶어버리는 한심한 결과**  

2. 트랜잭션의 경계설정 작업을 `UserService`쪽으로 가져옴  

### `UserService` 트랜잭션 경계설정의 문제점

2번의 경우, 트랜잭션의 문제는 해결할 수 있겠지만, 여러 가지 문제가 발생한다. 

1. JdbcTemplate을 더 이상 활용할 수 없고, 결국 JDBC API를 직접 사용하는 초기 방식으로 돌아간다.
2. `UserService`의 메소드에 `Connection`파라미터가 추가돼야 한다. 
3. `UserDao`는 더 이상 데이터 액세스 기술에 독립적일 수가 없다. 
4. 테스트 코드에도 영향을 미친다. 

## 트랜잭션 동기화 

지금까지 만들었던 깔끔하게 정리된 코드를 포기 vs 트랜잭션 기능을 포기 ?  
둘다 아니다. 스프링은 멋진 방법을 제공한다. 

### `Connection `파라미터 제거 

`upgradeLevels()`메소드가 트랜잭션 경계설정을 해야 한다는 사실은 피할 수 없다. 
 따라서 그 안에서 `Connection`을 생성하고 트랜잭션 시작과 종료를 관리하게 한다. 
 대신 여기서 생성된 `Connection` 오브젝트를 계속 메소드의 파라미터로 전달하다가 `DAO`를 호출할 때 사용하게 하는 건 피하고 싶다. 
 이를 위해 스프링이 제안하는 방법은 독립적인 **트랜잭션 동기화** 방식이다. 

 **트랜잭션 동기화**란 `UserServcie`에서 트랜잭션을 시작하기 위해 만든 `Connection`오브젝트를 특별한 저장소에 보관해두고, 
  이후에 호출되는 DAO의 메소드에서는 저장된 `Connection`을 가져다가 사용하게 하는 것이다. 

 트랜잭션 동기화 저장소는 작업 스레드마다 독립적으로 Connection 오브젝트를 저장하고 관리하기 때문에 다중 사용자를 처리하는 서버의 멀티스레드 환경에서도 충돌이 날 염려는 없다. 


 ### 트랜잭션 동기화 적용 

 ```java

    public void upgradeLevels() throws Exception{
        TransactionSynchronizationManager.initSynchronization();
        Connection c = DataSourceUtils.getConnection(dataSource);
        c.setAutoCommit(false);

        try {
            List<User> users = userDao.getAll();
            for (User user : users) {
                if (canUpgradeLevel(user)) {
                    upgradeLevel(user);
                }
            }
            c.commit();
        } catch (Exception e) {
            c.rollback();
            throw e;
        } finally {
            DataSourceUtils.releaseConnection(c, dataSource);
            TransactionSynchronizationManager.unbindResource(this.dataSource);
            TransactionSynchronizationManager.clearSynchronization();
        }

    }


트랜잭션 동기화가 되어 있는 채로 JdbcTemplate을 사용하면 JdbcTemplate의 작업에서 동기화시킨 DB커넥션을 사용하게 된다. JDBC의 트랜잭션 경계설정 메소드를 사용해 트랜잭션을 이용하는 전형적인 코드에 간단한 트랜잭션 동기화 작업만 붙여줌으로써, 지저분한 Connection 파라미터의 문제를 깔끔하게 해결했다.

트랜잭션 테스트 보안

테스트를 실행할 때는 추가된 DataSource에 대한 DI를 해준다.

JdbcTemplate과 트랜잭션 동기화

비즈니스 로직 레벨의 트랜잭션을 적용했지만 JdbcTemplate을 포기할 필요도 없고, 지저분한 Connection 파라미터를 계속 물고 다니지 않아도 된다. UserDao는 여전히 데이터 액세스 기술에 종속되지 않는 깔끔한 인터페이스 메소드를 유지하고 있다. 그리고 테스트에서 DAO를 직접 호출해서 사용하는 것도 아무런 문제기 되지 않는다.

트랜잭션 서비스 추상화

기술과 환경에 종속되는 트랜잭션 경계설정 코드

하나의 트랜잭션 안에서 여러 개의 DB에 데이터를 넣는 작업을 해야 할 필요가 발생했다. 각 DB와 독립적으로 만들어지는 Connection을 통해서가 아니라, 별도의 트랜잭션 관리자를 통해 트랜잭션을 관리하는 글로벌 트랜잭션 방식을 사용해야 한다.

자바는 글로벌 트랜잭션을 지원하는 트랜잭션 매니저를 지원하기 위한 API인 JTA(Java Transaction API) 를 제공한다.

트랜잭션 매니저는 DB와 메시징 서버를 제어하고 관리하는 각각의 리소스 매니저와 XA 프로토콜을 통해 연결된다.

이를 활용하면 여러 개의 DB나 메시징 서버에 대한 작업을 하나의 트랜잭션으로 통합하는 분산 트랜잭션 또는 글로벌 트랜잭션이 가능해진다.

트랜잭션 API의 의존관계 문제와 해결책

UserDao가 DAO 패턴을 사용해 구현 데이터 엑세스 기술을 유연하게 바꿔서 사용할 수 있게 했지만 UserSerivce에서 트랜잭션의 경계 설정을 해야 할 필요가 생기면서 다시 특정 데이터 액세스 기술에 종속되는 구조가 되고 말았다.

다힝해도 트랜잭션의 경계설정을 담당하는 코드는 일정한 패턴을 갖는 유사한 코드이기 때문에, 추상 계층이 제공하는 API를 이용해 트랜잭션을 이용하게 만들어준다면 특정 기술에 종속되지 않는 트랜잭션 경계설정 코드를 만들 수 있을 것이다.

스프링 트랜잭션 서비스의 추상화

스프링은 트랜잭션 기술의 공통점을 담은 트랜잭션 추상화 기술을 제공하고 있다. 이를 이용하면 애플리케이션에서 직접 각 기술의 트랜잭션 API를 이용하지 않고도, 일관된 방식으로 트랜잭션을 제어하는 트랜잭션 경계설정 작업이 가능해진다.

트랜잭션 기술 설정의 분리

트랜잭션 추상화 API를 적용한 UserService코드를 JTA를 이용하는 글로벌 트랜잭션으로 변경하려면 PlatformTransactionManger구현 클래스를 DataSourceTransactionManager에서 JTATransactionManager로 바꿔주기만 하면 된다.
하지만 어떤 트랜잭션 매니저 구현 클래스를 사용할지 UserService 코드가 알고 있는 것은 DI 원칙에 위배된다.

그러므로 DataSourceTransactionManger는 스프링 빈으로 등록하고, UserService가 DI방식으로 사용하게 해야 한다.

    <bean id="userService" class="spring.toby1.service.UserService">
        <property name="userDao" ref="userDao"/>
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>

트랜잭션을 JTA를 이용하는 것으로 고치고 싶다면 설정파일의 transactionManger 빈의 설정만 다음과 같이 고치면 된다.


    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManger">